import { Clock } from '$lib/clock';
import { AI, Collision, Creature, Description, Glyph, Impassable, Size } from '$lib/components';
import { log } from '$lib/log';
import { distribute, random, shuffle } from '$lib/random';
import { registerAI } from '$lib/simulate';
import { WaitAction } from '$lib/wait';
import { defineComponent, defineQuery, hasComponent, removeEntity } from '$lib/ecs';
import { getPos3, onTile, placeOnMap, registerLevelTrigger } from '$lib/pos3';
import { mapHeight, mapWidth } from './size';
import { logVisible } from '$lib/render3';
import { describe } from '$lib';
import { fromTemplate } from '$lib/builder';
import { Item, Rot } from '$lib/item';
import { Move3Action } from '$lib/walk3';
import { Diggable, Water } from './ground';
import { cleanup } from '$lib/cleanup';
import { Prey } from './attack';
import { Evasion } from './stats';
import { Heart } from '$lib/content/heart';

export const Worm = defineComponent('Worm', {});

const wormQuery = defineQuery([Worm]);

export const wormTemplate = [
  [Worm],
  [Creature],
  [Glyph, {
    glyph: 'c',
    color: '#edd',
  }],
  [Description, {
    summary: "earthworm",
    description: "Small invertebrate that usually dwell underground, but tend to infest the surface on a rainy day. Pass for an easy and tasty meal unless you're a vegan. Best served fresh and uncooked.",
  }],
  [Size, {
    size: 1,
  }],
  [Prey],
  [AI],
  [Clock],
  [Heart, {
    heartbeat: 20,
    elapsed: 0,
  }],
  [Evasion, {
    amount: 10,
  }],
];

export const wormCorpse = [
  [Item],
  [Glyph, {
    glyph: '%',
    color: '#edd',
  }],
  [Description, {
    summary: "earthworm corpse",
    description: "A dead body of common earthworm. Not as good as a fresh one, but might be still edible.",
  }],
  [Rot, {
    lifespan: 2000,
  }],
  [Clock],
];

const wormCanPass = (world, es) => {
  const hasCreature = es.some((e) => hasComponent(world, Creature, e));
  if (hasCreature)
    return false;
  const groundPass = es.every((e) =>
    !hasComponent(world, Water, e) &&
      !hasComponent(world, Impassable, e) &&
      (!hasComponent(world, Collision, e) || hasComponent(world, Diggable, e)));
  return es.length > 0 && groundPass;
};

const WormMove = (dx, dy, dz) => (world, e) => {
  const delay = Heart.heartbeat[e];
  return Move3Action(wormCanPass)(dx, dy, dz, delay)(world, e);
};

const wormMove = (world, e) => {
  let dx = 0;
  let dy = 0;
  const dz = distribute(random(), [
    [-1, 1],
    [0, 20],
    [1, 1],
  ]);
  if (dz === 0) {
    const r = Math.floor(random() * 8);
    dx = (r < 3) - (r > 4);
    dy = [0, 3, 5].includes(r) - [2, 4, 7].includes(r);
  }
  return WormMove(dx, dy, dz)(world, e);
};

// TODO: move to lib
const directions2D = [
  [-1, -1, 0],
  [0, -1, 0],
  [1, -1, 0],
  [-1, 0, 0],
  [1, 0, 0],
  [-1, 1, 0],
  [0, 1, 0],
  [1, 1, 0],
];

const wormSplit = (world, e) => {
  // find free space
  // TODO: this is generic util function, should be moved into a lib
  const {x, y, z} = getPos3(e);
  let target;
  let error: boolean|string = false;
  const dirs = shuffle(directions2D);
  while (target === undefined && dirs.length >= 0 && !error) {
    if (dirs.length === 0) {
      error = "No free space for worm splitting!";
    } else {
      const [[dx, dy, dz]] = dirs.splice(0, 1);
      const x1 = x+dx;
      const y1 = y+dy;
      const z1 = z+dz;
      if (wormCanPass(world, onTile(world, x1, y1, z1))) {
        target = [x1, y1, z1];
      }
    }
  }
  return {
    error,
    action: () => {
      Clock.time[e] += Heart.heartbeat[e];
      if (error)
        return;
      const [x1, y1, z1] = target;
      const clone = fromTemplate(world, wormTemplate);
      placeOnMap(world, clone, x1, y1, z1);
    },
  };
};

const suicide = (world, e) => {
  return {
    error: false,
    action: () => {
      const {x, y, z} = getPos3(e);
      const corpse = fromTemplate(world, wormCorpse);
      placeOnMap(world, corpse, x, y, z);
      cleanup(world, e);
      removeEntity(world, e);
    },
  };
};

const wormCount = new Map();

registerLevelTrigger(Worm, (world, e, z0, z1) => {
  if (z0 !== undefined) {
    wormCount.set(z0, wormCount.get(z0)-1);
  }
  if (z1 !== undefined) {
    wormCount.set(z1, (wormCount.get(z1) ?? 0) + 1);
  }
});

const countWorms = (world, z) => {
  return wormCount.get(z) ?? 0;
};

registerAI({
  target: Worm,
  action: (world, e) => {
    const {x, y, z} = getPos3(e);
    if (z !== 0) {
      const count = countWorms(world, z);
      if (count < mapWidth*mapHeight/64) {
        logVisible(e, "worm splits in two!");
        return wormSplit(world, e);
      } else if (count > mapWidth*mapHeight/16) {
        logVisible(e, `${describe(world, e)} dies of overpopulation`);
        return suicide(world, e);
      }
    }
    return wormMove(world, e);
  },
});
